Skip to content

feat: add plugin JWT Decoder - #12

Open
Chiloute wants to merge 3 commits into
anotherhadi:mainfrom
Chiloute:feat-jwt-decoder-plugin
Open

feat: add plugin JWT Decoder#12
Chiloute wants to merge 3 commits into
anotherhadi:mainfrom
Chiloute:feat-jwt-decoder-plugin

Conversation

@Chiloute

Copy link
Copy Markdown

Close #11

What

Adds a plugin (plugins/jwt_decoder.lua) that decodes JWTs found in request headers.

Unlike the original proposal (which only mentioned the Authorization header), this
implementation scans every request header. For any header value that looks like
a JWT (three dot-separated base64url segments, signature segment optional for
alg: none), the decoded payload is added back to the request as a new header:

Authorization: Bearer eyJ...   ->   X-JWT-Decoded-Authorization: {...}
X-Auth-Token:  eyJ...          ->   X-JWT-Decoded-X-Auth-Token:  {...}

A leading Bearer prefix is stripped before detection, so both standard and
custom auth headers are handled.

Details

  • Pure Lua, no external dependencies.
  • Payload is validated as plausible JSON (^%s*{) before being accepted, to
    avoid false positives on non-JWT dot-separated strings.
  • No signature verification — this only decodes the payload for inspection/debugging.
    The decoded value should not be treated as verified/trusted.
  • Decoded header is added to the outbound request, so it's visible in intercept,
    history, and replay views.

Copilot AI review requested due to automatic review settings July 21, 2026 17:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Spilltea Lua plugin that scans request headers for JWT-looking values, decodes the base64url payload, and exposes the decoded payload by injecting an X-JWT-Decoded-* header into the outbound request (to show up in intercept/history/replay).

Changes:

  • Introduces plugins/jwt_decoder.lua implementing base64url decoding and JWT payload extraction.
  • Scans all request headers (optionally stripping a leading Bearer prefix) and adds decoded payloads back as new request headers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread plugins/jwt_decoder.lua Outdated
B64_DEC[B64_ALPHABET:sub(i, i)] = i - 1
end

-- Decode an unpadded base64url string. Returns the decoded string, or nil if
Comment thread plugins/jwt_decoder.lua
Comment thread plugins/jwt_decoder.lua Outdated
if decoded:match("^%s*{") == nil then
return nil
end
decoded = decoded:gsub("[\r\n]+", " ")
Co-authored-by: Copilot Autofix powered by AI <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

plugins/jwt_decoder.lua:58

  • decode_jwt_payload decodes attacker-controlled header values without any size limits. A large header that matches the JWT shape can cause large allocations (base64 decode + decoded string) and can also create an oversized outbound X-JWT-Decoded-* header that may break forwarding through proxies/servers with header-size limits.

Consider bounding the work and skipping decoding when the token/payload/decoded JSON exceeds a reasonable limit (e.g., 8KB).

local function decode_jwt_payload(value)
	-- Three dot-separated base64url segments; the signature may be empty (alg:none).
	local payload = value:match("^[A-Za-z0-9_-]+%.([A-Za-z0-9_-]+)%.[A-Za-z0-9_-]*$")
	if not payload then
		return nil

plugins/jwt_decoder.lua:51

  • b64url_decode currently accepts inputs with non-zero leftover bits (i.e., invalid base64url when unpadded). That can decode malformed payload segments and increases the chance of false positives (or inconsistent decoding) when scanning arbitrary header values.

Consider rejecting inputs where leftover bits remain and are not all zero.

	end
	return table.concat(out)
end

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (2)

plugins/jwt_decoder.lua:48

  • b64url_decode only emits a single byte when bits >= 8. Because base64url adds 6 bits per character, bits can exceed 16 and should emit multiple bytes per iteration; using if here produces incorrect decoding for most inputs (and can lead to invalid byte values passed to string.char).
		if bits >= 8 then
			bits = bits - 8
			out[#out + 1] = string.char(math.floor(acc / 2 ^ bits))
			acc = acc % 2 ^ bits
		end

plugins/jwt_decoder.lua:59

  • The PR description says the signature segment is optional for alg:none, but the current pattern requires a trailing . (i.e., always 3 segments). If you want to support 2-segment tokens (header.payload) as well, the match needs to allow that form.
	-- Three dot-separated base64url segments; the signature may be empty (alg:none).
	local payload = value:match("^[A-Za-z0-9_-]+%.([A-Za-z0-9_-]+)%.[A-Za-z0-9_-]*$")
	if not payload or #payload > 8192 then
		return nil
	end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[PLUGIN] JWT Decoder

2 participants